-module(katzenreis_filters).

-export([filter_value/5]).

-include("katzenreis.hrl").

-import(katzenreis_callbacks, [get_custom_filter/1, set_custom_filter/2]).
-import(katzenreis_render, [get_default_value/2, get_var/2, get_var_default/3,
        is_safe/1, make_safe/1,
        render_file/2, render_file/3, render_nodes/3, render_nodes/4,
        render_str/2, render_str/3, parse_nodes/4, parse_bool/3,
        parse_value/3, tag_arith_error/4, to_bool/1, print/1]).
-import(katzenreis_utils, [escape_html/1, escape_quotes/1,
  is_string/1, unquote_string/1]).


%% @doc Returns a value which is result of applying filters to `Value'.
%% @spec (Value::term(), Attrs::attirbutes(), Filters::[filter_token()],
%%  Context::context(), Options::options()) ->
%%  FilteredValue::{proplist(), term()}
%% @throws render_error()
%% @see render_nodes/4
%% @since #since

-spec filter_value(term(), attributes(), [filter_token()],
  context(), options()) -> {proplist(), term()}.

filter_value(Value, Attrs, [], _Context, _Options) -> {Value, Attrs};
filter_value(Value, Attrs, [{Line, Name, Arg}|Filters], Context, Options) ->
  {Value1, Attrs1} = filter_value1(Value, Attrs, Line, Name, Arg,
    Context, Options),
  filter_value(Value1, Attrs1, Filters, Context, Options).

filter_value1(Value, Attrs, Line, Name, Arg, Context, Options) ->
  Arg1 = get_filter_arg(Arg, Context, Options),
  case Name of
    'add' -> filter_add(Value, Attrs, Line, Arg1, Context, Options);
    'addslashes' ->
      filter_addslashes(Value, Attrs, Line, Arg1, Context, Options);
    'capfirst' -> filter_capfirst(Value, Attrs, Context, Options);
    'center' -> filter_center(Value, Attrs, Arg1, Context, Options);
    'default' -> filter_default(Value, Attrs, Line, Arg1, Context, Options);
    'default_if_none' -> filter_default_if_none(Value, Attrs, Arg1,
      Context, Options);
    'first' -> filter_first(Value, Attrs, Line, Arg1, Context, Options);
    'join' -> filter_join(Value, Attrs, Line, Arg1, Context, Options);
    'last' -> filter_last(Value, Attrs, Line, Arg1, Context, Options);
    'length' -> filter_length(Value, Attrs, Line, Arg1, Context, Options);
    'ljust' -> filter_ljust(Value, Attrs, Arg1, Context, Options);
    'lower' -> filter_lower(Value, Attrs, Line, Arg1, Context, Options);
    'rjust' -> filter_rjust(Value, Attrs, Arg1, Context, Options);
    'upper' -> filter_upper(Value, Attrs, Line, Arg1, Context, Options);
    'safe' -> filter_safe(Value, Attrs, Line, Arg1, Context, Options);
    _ ->
      case get_custom_filter(Name) of
        {M, F} -> apply(M, F, [Value, Attrs, Arg1, Context, Options]);
        undefined -> throw({render_error, Line, "unknown filter `" ++
          atom_to_list(Name) ++ "'"})
      end
  end.

get_filter_arg(true, _Context, _Options) -> true;
get_filter_arg(false, _Context, _Options) -> false;
get_filter_arg(Arg, Context, Options) when is_atom(Arg) ->
  get_var_default(Arg, Context, Options);
get_filter_arg(Arg, _Context, _Options) -> Arg.

filter_add(Value, Attrs, _Line, Arg, Context, Options)
  when is_integer(Value) ->
  Value1 = if
    is_integer(Arg) -> Value + Arg;
    is_float(Arg) -> Value + Arg;
    is_list(Arg) -> Value + list_to_integer(Arg);
    true -> get_default_value(Context, Options)
  end,
  {Value1, Attrs};
filter_add(_Value, Attrs, _Line, _Arg, Context, Options) ->
  {get_default_value(Context, Options), Attrs}.

filter_addslashes(Value, Attrs, _Line, _Arg, _Context, _Options) ->
  {escape_quotes(print(Value)), Attrs}.

filter_capfirst([C|Cs], Attrs, _Context, _Options) ->
  Value1 = string:to_upper([C]) ++ Cs,
  {Value1, Attrs}.

filter_center(Value, Attrs, Arg1, _Context, _Options) when is_integer(Arg1) ->
  Value1 = print(Value),
  Value2 = string:centre(Value1, Arg1),
  {Value2, Attrs};
filter_center(Value, Attrs, _Arg1, _Context, _Options) ->
  {Value, Attrs}.

filter_default(Value, Attrs, _Line, Arg, Context, Options) ->
  Value1 = case Value =:= [] orelse Value =:= false of
    true ->
      case Arg of
        nil -> get_default_value(Context, Options);
        _ -> Arg
      end;
    false -> Value
  end,
  {Value1, Attrs}.

filter_default_if_none(nil, Attrs, Arg1, _Context, _Options) ->
  {Arg1, Attrs};
filter_default_if_none(Value, Attrs, _Arg1, _Context, _Options) ->
  {Value, Attrs}.

filter_first(Value, Attrs, _Line, _Arg, _Context, _Options)
  when is_list(Value) ->
  {hd(Value), Attrs};
filter_first(_Value, Attrs, _Line, _Arg, Context, Options) ->
  {get_default_value(Context, Options), Attrs}.

filter_join(Value, Attrs, _Line, Sep, _Context, _Options) ->
  {string:join(Value, Sep), Attrs}.

filter_last(Value, Attrs, _Line, _Arg, _Context, _Options)
  when is_list(Value) ->
  {lists:last(Value), Attrs};
filter_last(_Value, Attrs, _Line, _Arg, Context, Options) ->
  {get_default_value(Context, Options), Attrs}.

filter_length(Value, Attrs, _Line, _Arg, _Context, _Options)
  when is_list(Value) ->
  {length(Value), Attrs};
filter_length(_Value, Attrs, _Line, _Arg, Context, Options) ->
  {get_default_value(Context, Options), Attrs}.

filter_ljust(Value, Attrs, Arg1, _Context, _Options) when is_integer(Arg1) ->
  Value1 = print(Value),
  Value2 = string:left(Value1, Arg1),
  {Value2, Attrs};
filter_ljust(Value, Attrs, _Arg1, _Context, _Options) ->
  {Value, Attrs}.

filter_lower(Value, Attrs, _Line, _Arg, _Context, _Options) ->
  Value1 = print(Value),
  {string:to_lower(Value1), Attrs}.

filter_rjust(Value, Attrs, Arg1, _Context, _Options) when is_integer(Arg1) ->
  Value1 = print(Value),
  Value2 = string:right(Value1, Arg1),
  {Value2, Attrs};
filter_rjust(Value, Attrs, _Arg1, _Context, _Options) ->
  {Value, Attrs}.

filter_upper(Value, Attrs, _Line, _Arg, _Context, _Options) ->
  Value1 = print(Value),
  {string:to_upper(Value1), Attrs}.

filter_safe(Value, Attrs, _Line, _Arg, _Context, _Options) ->
  {Value, make_safe(Attrs)}.

